package com.owen.util;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.net.URLEncoder;
import cn.hutool.crypto.SecureUtil;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;

/**
 * 利用Flt导出Excel工具类
 *
 * @author lby
 * @create 2024/1/4 15:36
 */
public class FtlExcelUtil {

    // 日志
    private static final Logger logger = LoggerFactory.getLogger(FtlExcelUtil.class);

    // 模板文件生成到磁盘的文件路径
    private static final String excelTemplatesDirectory = "c:/var/excelTemplates";

    /**
     * 利用flt模板下载Excel方法
     *
     * @param templatePath flt模板路径 【/excelTemplates/phone/phoneTemplate.ftl】
     * @param excelName    导出excel文件名
     * @param data         动态数据
     * @param response
     * @throws IOException
     * @throws TemplateException
     */
    public static void downloadExcel(String templatePath, String excelName, Map<String, Object> data, HttpServletResponse response) throws IOException, TemplateException {
        // 项目中模板文件生成到本地磁盘 (返回值为ftl文件的路径)
        String fileDirectory = copyTemplateToDisk(templatePath);
        Path templateNamePath = Paths.get(templatePath);
        // 获取模板名
        String templateName = templateNamePath.getFileName().toString();
        // 初始化配置文件
        Configuration configuration = new Configuration();
        // 设置编码
        configuration.setDefaultEncoding("utf-8");
        // 设置为空不报错
        configuration.setClassicCompatible(true);

        // 加载模板文件
        configuration.setDirectoryForTemplateLoading(new File(fileDirectory));

        // 加载模板，通过Word转XML文件转换过来的
        Template template = configuration.getTemplate(templateName);
        response.setContentType("application/octet-stream;charset=utf-8");
        response.setHeader("content-disposition", "attachment;fileName=" + URLEncoder.createDefault().encode(excelName + ".xlsx", StandardCharsets.UTF_8));
        template.process(data, new OutputStreamWriter(response.getOutputStream(), StandardCharsets.UTF_8));
        response.flushBuffer();
    }

    /**
     * ftl的excel模板文件生成到本地磁盘
     *
     * @param templatePath ftl模板文件在resources中的相对地址
     * @return 该模板文件生成到本地磁盘的目录地址
     */
    public static String copyTemplateToDisk(String templatePath) {
        // 构建目标文件路径
        File diskTemplate = new File(excelTemplatesDirectory + File.separator + templatePath);
        Path diskPath = diskTemplate.toPath();

        // 检查文件是否存在及其MD5值是否匹配
        boolean diskExists = diskTemplate.exists();
        boolean md5EqFlag = false;

        if (diskExists) {
            try (FileInputStream diskInputStream = new FileInputStream(diskTemplate);
                 InputStream templateInputStream = new ClassPathResource(templatePath).getInputStream()) {
                String diskMd5 = SecureUtil.md5(diskInputStream);
                String templateMd5 = SecureUtil.md5(templateInputStream);
                md5EqFlag = diskMd5.equals(templateMd5);
            } catch (IOException e) {
                throw new RuntimeException("Error while comparing MD5 hashes", e);
            }
        }

        // 如果文件不存在或MD5不匹配，则重新生成文件
        if (!diskExists || !md5EqFlag) {
            if (!diskExists) {
                logger.info("{} 文件在 {} 文件夹中不存在，已在该文件中生成该文件", templatePath, excelTemplatesDirectory);
            } else {
                logger.info("系统对比 {} 文件已变动，准备生成新文件", templatePath);
                // 删除旧文件
                if (diskTemplate.delete()) {
                    logger.info("删除原磁盘文件成功。");
                } else {
                    logger.warn("无法删除原磁盘文件。");
                }
            }

            // 确保目标目录存在（目标目录不存在则创建该目录）
            createDirectoriesIfNotExists(diskPath.getParent().toFile());
            try (InputStream is = new ClassPathResource(templatePath).getInputStream();
                 OutputStream os = Files.newOutputStream(diskPath)) {
                IoUtil.copy(is, os);
                logger.info("{} 文件生成成功", templatePath);
            } catch (IOException e) {
                throw new RuntimeException("Error while copying template file", e);
            }
        }
        // 返回该模板所在文件夹
        return diskPath.getParent().toString();
    }

    /**
     * 创建目录（如果不存在）
     *
     * @param directory 目录对象
     */
    private static void createDirectoriesIfNotExists(File directory) {
        if (!directory.isDirectory() && !directory.mkdirs()) {
            logger.error("无法创建目录: {}", directory.getAbsolutePath());
        }
    }
}
